package com.have.blog.web.service.impl;

import com.have.blog.base.enums.BlogLikeEnum;
import com.have.blog.base.wrapper.Wrapper;
import com.have.blog.config.hloder.RequestHolder;
import com.have.blog.model.mapper.BlogLikeMapper;
import com.have.blog.model.models.BlogLike;
import com.have.blog.model.models.BlogLikeExample;
import com.have.blog.web.constant.RedisConstant;
import com.have.blog.web.redis.LikeRedisService;
import com.have.blog.web.service.LikeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.List;

@Service
@SuppressWarnings("all")
public class LikeServiceImpl implements LikeService {
    @Autowired
    private LikeRedisService likeRedisService;
    @Autowired
    private BlogLikeMapper likeMapper;
    @Value("${redis.admin.database}")
    private String REDIS_KEY_DATABASE;
    @Autowired
    private RedisTemplate redisTemplate;
    private String LUA_SCRIPT_FOR_BLOG_LIKE = "local count = redis.call('zScore', KEYS[1], ARGV[1]);\n" + // 1
            "local status = redis.call('HGET', KEYS[2], KEYS[3]);\n" + //2
            "if not status or tonumber(status) == 0 then\n" + //3
            "redis.call('zincrby', KEYS[1], 1, ARGV[1]);\n" + //4
            "redis.call('hSet', KEYS[2], KEYS[3], 1);\n" + //5
            "count = count + 1;\n" + //6
            "else\n" + // 7
            "if tonumber(count) > 0 then\n" + // 8
            "redis.call('zincrby', KEYS[1], -1,  ARGV[1]);\n" +
            "end;\n" + // 9
            "redis.call('hSet', KEYS[2], KEYS[3], 0);\n" +
            "count = count - 1;\n" + // 10
            "end;\n" + // 11
            "return count;\n"; // 12

    /**
     * 使用lua脚本保证用户点赞的原子性
     * @param blogUid
     * @return
     */
    @Override
    public Wrapper likeOrCancelByBlogUid(Integer blogUid) {
        Integer userUid = RequestHolder.getUserUid();
        String key =  REDIS_KEY_DATABASE + RedisConstant.SEG
                + RedisConstant.BLOG_LIKE;
        String countKey = REDIS_KEY_DATABASE + RedisConstant.SEG
                + RedisConstant.BLOG_LIKE + RedisConstant.SEG
                + RedisConstant.COUNT_KEY;
        String hKey = blogUid + RedisConstant.SEG + userUid;
        String blogKey = String.valueOf(blogUid);

        // 缓存中不存在就加入
        if (redisTemplate.opsForHash().get(key, hKey) == null) {
            Integer status = getUserLikeStatus(blogUid, userUid);
            redisTemplate.opsForHash().put(key, hKey, status);
        }
        if (redisTemplate.opsForZSet().score(countKey, blogKey) == null) {
           getLikeCountByBlogUid(blogUid);
        }
        Long count = (Long) redisTemplate.execute(new RedisCallback() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.eval(LUA_SCRIPT_FOR_BLOG_LIKE.getBytes(),
                        ReturnType.fromJavaType(Long.class), 3, countKey.getBytes(), key.getBytes(), hKey.getBytes(), blogKey.getBytes());
            }
        });
        return Wrapper.success(count);
    }

    @Override
    public Long getLikeCountByBlogUid(Integer blogUid) {
        Double blogLikeCount = likeRedisService.getBlogLikeCount(blogUid);
        if (blogLikeCount != null) return blogLikeCount.longValue();
        BlogLikeExample example = new BlogLikeExample();
        example.createCriteria()
                .andBlogUidEqualTo(blogUid)
                .andStatusEqualTo(BlogLikeEnum.LIKE.getStatus());
        Long count = likeMapper.countByExample(example);
        likeRedisService.putBlogLikeCount(blogUid, count.doubleValue());
        return count;
    }

    private Integer getUserLikeStatus(Integer blogUid, Integer userUid) {
        BlogLikeExample example = new BlogLikeExample();
        example.createCriteria()
                .andBlogUidEqualTo(blogUid)
                .andUserUidEqualTo(userUid);
        List<BlogLike> blogLikes = likeMapper.selectByExample(example);
        if (CollectionUtils.isEmpty(blogLikes)) return 0;
        return blogLikes.get(0).getStatus();
    }

    @Transactional
    @Override
    public void transLikedFromRedis2DB() {
        List<BlogLike> allLikeFromRedis = likeRedisService.getAllLikeFromRedis();
        for (BlogLike item : allLikeFromRedis) {
            BlogLikeExample example = new BlogLikeExample();
            example.createCriteria()
                    .andBlogUidEqualTo(item.getBlogUid())
                    .andUserUidEqualTo(item.getUserUid());
            List<BlogLike> blogLikeList = likeMapper.selectByExample(example);
            if (CollectionUtils.isEmpty(blogLikeList)) {
                likeMapper.insertSelective(item);
            } else {
                BlogLike blogLike = blogLikeList.get(0);
                blogLike.setStatus(item.getStatus());
                likeMapper.updateByPrimaryKeySelective(blogLike);
            }
        }
    }
}
